import { sRGB_to_LCH, LCH_to_sRGB } from './chroma';

// WCAG 2.1
// Minimum ratio (AA rating)	Enhanced ratio (AAA rating)
// Body text: AA(Minimum) > 4.5, AAA(Enhanced) > 7
// Large-scale text AA(Minimum) > 3, AAA(Enhanced) > 4.5

function adjustGamma(_) {
	return ((_ + 0.055) / 1.055) ** 2.4;
}

function calcLuminance(rgb) {
	// R, G, B varying from 0 - 255, return the luminance [0 - 1]
	const rc = 0.2126;
	const gc = 0.7152;
	const bc = 0.0722;
	const lowc = 1 / 12.92; // low-gamma adjust coefficient

	const rsrgb = rgb[0] / 255;
	const gsrgb = rgb[1] / 255;
	const bsrgb = rgb[2] / 255;
	const r = rsrgb <= 0.03928 ? rsrgb * lowc : adjustGamma(rsrgb);
	const g = gsrgb <= 0.03928 ? gsrgb * lowc : adjustGamma(gsrgb);
	const b = bsrgb <= 0.03928 ? bsrgb * lowc : adjustGamma(bsrgb);
	return r * rc + g * gc + b * bc;
}

function compareLuminance(a, b) {
	const l1 = Math.max(a, b);
	const l2 = Math.min(a, b);
	return (l1 + 0.05) / (l2 + 0.05);
}

function calcAccessibilityContrast(rgb1, rgb2) {
	// e.g rgb [0-255], return luminance [0 - 1]
	return compareLuminance(calcLuminance(rgb1), calcLuminance(rgb2));
}

function calcAccessibilityScore(contrast) {
	if (contrast >= 7) {
		return 'AAA';
	}
	if (contrast >= 4.5) {
		return 'AA';
	}
	if (contrast >= 3) {
		return 'AA Large';
	}
	return 'Fail';
}

function isAccessible(rgb1, rgb2) {
	const contrast = calcAccessibilityContrast(rgb1, rgb2);
	return contrast > 4.5;
}

// Nearest Color
// e.g rgb[0-255]
function calcColorDistance(rgb1, rgb2) {
	return Math.sqrt((rgb1[0] - rgb2[0]) ** 2 + (rgb1[1] - rgb2[1]) ** 2 + (rgb1[2] - rgb2[2]) ** 2);
}

function calcNearestColor(colorArr, inputColor) {
	// e.g ['#ff0000', '#ff00ff'], '#ffcc00'
	const res = [];
	for (const col of colorArr) {
		res.push(calcColorDistance(colorToRGB(inputColor), colorToRGB(col)));
	}
	const nearest = colorArr[res.indexOf(Math.min.apply(null, res))];
	return nearest;
}

function calcBrightness(col) {
	// e.g: "#FF0000"
	const rgb = colorToRGB(col);
	return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
}

function isGray(col) {
	const rgb = colorToRGB(col);
	return rgb[0] == rgb[1] && rgb[0] == rgb[2] && rgb[1] == rgb[2];
}

function isDark(col) {
	return calcBrightness(col) < 128;
}

function isLight(col) {
	return !isDark(col);
}

function isValidColorString(val) {
	return /^(#|0x)([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/i.test(val);
}

function colorToHexstr(col) {
	// e.g: "#FF0000" - "0xFF0000"
	return col.replace('#', '0x');
}

function colorToHex(col) {
	// e.g: "#FF0000" - 0xFF0000 (uint)
	return parseInt(col.replace('#', '0x'));
}

function colorToRGB(col) {
	// e.g: "#FF0000" or #FF0" - [ 255, 0, 0 ];
	const regexp = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
	col = col.replace(regexp, (m, r, g, b) => r + r + g + g + b + b);
	const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(col);
	return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
}

function rgbToGrayscale(r, g, b) {
	if (Array.isArray(r)) {
		const arr = r;
		r = arr[0];
		g = arr[1];
		b = arr[2];
	}
	// const grayscale = Math.round((r+g+b)/3);
	const grayscale = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
	return grayscale;
}

function rgbToColor(r, g, b) {
	if (Array.isArray(r)) {
		const arr = r;
		r = arr[0];
		g = arr[1];
		b = arr[2];
	}
	// e.g: 255, 0, 0 - "#FF0000"
	return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}

function rgbToHSL(r, g, b) {
	if (Array.isArray(r)) {
		const arr = r;
		r = arr[0];
		g = arr[1];
		b = arr[2];
	}
	// e.g: r, g, b( 0 - 255), return [ h, s, l ]; (0 - 1)
	// hue:0 - 1; saturation:0 - 1; lightness: 0 - 1;
	(r /= 255), (g /= 255), (b /= 255);
	const max = Math.max(r, g, b);
	const min = Math.min(r, g, b);
	let h;
	let s;
	const l = (max + min) / 2;
	if (max == min) {
		h = s = 0;
	} else {
		const d = max - min;
		s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
		switch (max) {
			case r:
				h = (g - b) / d + (g < b ? 6 : 0);
				break;
			case g:
				h = (b - r) / d + 2;
				break;
			case b:
				h = (r - g) / d + 4;
				break;
		}
		h /= 6;
	}
	return [h, s, l];
}

function colorToHSL(col) {
	// e.g: "#ff9900" to [ h, s, l ];
	const rgb = colorToRGB(col);
	return rgbToHSL(rgb[0], rgb[1], rgb[2]);
}

function hexToARGB(hex) {
	// e.g: 0x00FF0000 - [0, 255, 0, 0]
	const a = hex >>> 24;
	const r = (hex >> 16) & 0xff;
	const g = (hex >> 8) & 0xff;
	const b = hex & 0x000000ff;
	return [a, r, g, b];
}

function hexToColor(hex) {
	// e.g: 0xFF0000 (uint) - "#FF0000"
	const r = (hex & 0xff0000) >> 16;
	const g = (hex & 0x00ff00) >> 8;
	const b = hex & 0x0000ff;
	return `#${r < 16 ? '0' : ''}${r.toString(16)}${g < 16 ? '0' : ''}${g.toString(16)}${b < 16 ? '0' : ''}${b.toString(
		16
	)}`;
}

function hexToHexstr(numUint) {
	// e.g: 13421772 (uint) - "0xFF0000";
	return `0x${numUint.toString(16)}`;
}

function hexToRGB(hex) {
	// e.g: 0xFF0000 (uint) - [255, 0, 0];
	return [(hex & 0xff0000) >> 16, (hex & 0x00ff00) >> 8, hex & 0x0000ff];
}

function hexToRGBA(hex) {
	// e.g: 0x00FF0000 - [255, 0, 0, 255]
	const r = hex >>> 24;
	const g = (hex >> 16) & 0xff;
	const b = (hex >> 8) & 0xff;
	const a = hex & 0x000000ff;
	return [r, g, b, a];
}

function hexToString(hex, pfx = '#') {
	return pfx + `000000${Math.floor(hex).toString(16)}`.slice(-6).toUpperCase();
}

function hueToRGB(m1, m2, hue) {
	// e.g: return 0-255
	// m1:0 - 255, m2:0 - 255, hue: 0 - 255;
	hue = hue < 0 ? hue + 1 : hue > 1 ? hue - 1 : hue;
	if (hue * 6 < 1) return m1 + (m2 - m1) * hue * 6;
	if (hue * 2 < 1) return m2;
	if (hue * 3 < 2) return m1 + (m2 - m1) * (0.66666 - hue) * 6;
	return m1;
}

function hslToRGB(hue, sat, lgt) {
	// e.g: h, s, l (0-1) - to rgb[ 255, 0, 0 ]
	// hue:0 - 1; saturation:0 - 1; lightness: 0 - 1;
	const m2 = lgt <= 0.5 ? lgt * (sat + 1) : lgt + sat - lgt * sat;
	const m1 = lgt * 2 - m2;
	return [
		Math.round(hueToRGB(m1, m2, hue + 0.33333) * 255),
		Math.round(hueToRGB(m1, m2, hue) * 255),
		Math.round(hueToRGB(m1, m2, hue - 0.33333) * 255)
	];
}

function hslToColor(hue, sat, lgt) {
	// h, s, l (0-1)
	const rgb = hslToRGB(hue, sat, lgt);
	return rgbToColor(rgb);
}

function rgbToHex(r, g, b) {
	if (Array.isArray(r)) {
		const arr = r;
		r = arr[0];
		g = arr[1];
		b = arr[2];
	}
	// e.g: 255, 0, 0 - 0xFF0000 (uint)
	return (r << 16) | (g << 8) | b;
}

function rgbToHexstr(r, g, b) {
	// e.g: 255,255,255 - "0xFFFFFF"
	return `0x${r < 16 ? '0' : ''}${r.toString(16)}${g < 16 ? '0' : ''}${g.toString(16)}${
		b < 16 ? '0' : ''
	}${b.toString(16)}`;
}

function rgbaToHexstr(r, g, b, cA) {
	// e.g: 255,0,0,255 - "0xFF0000FF"
	return `0x${r < 16 ? '0' : ''}${r.toString(16)}${g < 16 ? '0' : ''}${g.toString(16)}${
		b < 16 ? '0' : ''
	}${b.toString(16)}${cA < 16 ? '0' : ''}${cA.toString(16)}`;
}

function rgba(col, alpha) {
	// e.g: rgba("#ff0000", 0.3) | rgba("0xff0000", 0.3) | rgba(0xff0000, 0.3)
	// get -> "rgba(255,0,0,0.3)" for css
	let r;
	let g;
	let b;
	const a = Math.min(1, Math.max(0, alpha));
	if (typeof col == 'string') {
		col = col.charAt(0) == '#' ? `0x${col.substr(1)}` : col;
		r = (parseInt(col) & 0xff0000) >> 16;
		g = (parseInt(col) & 0x00ff00) >> 8;
		b = parseInt(col) & 0x0000ff;
	} else {
		r = (col & 0xff0000) >> 16;
		g = (col & 0x00ff00) >> 8;
		b = col & 0x0000ff;
	}
	return `rgba(${r}, ${g}, ${b}, ${a.toString()})`;
}

// [h, s, v] array [0-1], return RGB[0-255]
function hsvToRGB(arr) {
	const h = arr[0];
	const s = arr[1];
	const v = arr[2];
	let r;
	let g;
	let b;
	let i;
	let f;
	let p;
	let q;
	let t;
	i = Math.floor(h * 6);
	f = h * 6 - i;
	p = v * (1 - s);
	q = v * (1 - f * s);
	t = v * (1 - (1 - f) * s);
	if (isNaN(i)) i = 0;
	if (isNaN(q)) q = 0;
	if (isNaN(t)) t = 0;
	switch (i % 6) {
		case 0:
			(r = v), (g = t), (b = p);
			break;
		case 1:
			(r = q), (g = v), (b = p);
			break;
		case 2:
			(r = p), (g = v), (b = t);
			break;
		case 3:
			(r = p), (g = q), (b = v);
			break;
		case 4:
			(r = t), (g = p), (b = v);
			break;
		case 5:
			(r = v), (g = p), (b = q);
			break;
	}
	return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}

function rgbToHSV(arr) {
	// [r, g, b] [0-255], return hsv[0-1]
	const r = arr[0];
	const g = arr[1];
	const b = arr[2];
	const max = Math.max(r, g, b);
	const min = Math.min(r, g, b);
	const d = max - min;
	const s = max === 0 ? 0 : d / max;
	const v = max / 255;
	let h;
	switch (max) {
		case min:
			h = 0;
			break;
		case r:
			h = g - b + d * (g < b ? 6 : 0);
			h /= 6 * d;
			break;
		case g:
			h = b - r + d * 2;
			h /= 6 * d;
			break;
		case b:
			h = r - g + d * 4;
			h /= 6 * d;
			break;
	}

	return [h, s, v];
}

function hsvToColor(arr) {
	const rgb = hsvToRGB(arr);
	return rgbToColor(rgb);
}

function colorToHSV(color) {
	const rgb = colorToRGB(color);
	return rgbToHSV(rgb);
}

function rgbToCMYK(r, g, b) {
	if (Array.isArray(r)) {
		const arr = r;
		r = arr[0];
		g = arr[1];
		b = arr[2];
	}

	// rgb 0-255, return cmyk 0-100
	let c = 0;
	let m = 0;
	let y = 0;
	let k = 0;

	// black
	if (r == 0 && g == 0 && b == 0) {
		k = 1;
		return [0, 0, 0, 1];
	}

	c = 1 - r / 255;
	m = 1 - g / 255;
	y = 1 - b / 255;

	const min_cmy = Math.min(c, Math.min(m, y));
	c = (c - min_cmy) / (1 - min_cmy);
	m = (m - min_cmy) / (1 - min_cmy);
	y = (y - min_cmy) / (1 - min_cmy);
	k = min_cmy;

	c = isNaN(c) ? 0 : c;
	m = isNaN(m) ? 0 : m;
	y = isNaN(y) ? 0 : y;
	k = isNaN(k) ? 0 : k;

	c = ~~(c * 10000) / 100;
	m = ~~(m * 10000) / 100;
	y = ~~(y * 10000) / 100;
	k = ~~(k * 10000) / 100;

	return [c, m, y, k];
}

function cmykToRGB(c, m, y, k) {
	if (Array.isArray(c)) {
		const arr = c;
		c = arr[0];
		m = arr[1];
		y = arr[2];
		k = arr[3];
	}

	// cmyk 0 -100, return rgb 0-255
	c /= 100;
	m /= 100;
	y /= 100;
	k /= 100;

	c = c * (1 - k) + k;
	m = m * (1 - k) + k;
	y = y * (1 - k) + k;

	let r = Math.round(255 * (1 - c));
	let g = Math.round(255 * (1 - m));
	let b = Math.round(255 * (1 - y));

	return [r, g, b];
}

function rgbToLCH(r, g, b) {
	if (Array.isArray(r)) {
		const arr = r;
		r = arr[0];
		g = arr[1];
		b = arr[2];
	}
	// rgb is 0-255, return lch 0-1
	const lch = sRGB_to_LCH([r / 255, g / 255, b / 255]);
	return [lch[0] / 100, lch[1] / 132, lch[2] / 360];
}

function lchToRGB(l, c, h) {
	// lch is 0-1, return rgb 0-255
	const rgb = LCH_to_sRGB([l * 100, c * 132, h * 360]);
	return [rgb[0] * 255, rgb[1] * 255, rgb[2] * 255];
}

function formatColor(hex) {
	// 3 digit to 6 digit, e.g #ff9 -> #ffff99
	let prefix = '#';
	if (typeof hex == 'string') {
		hex = hex.toLowerCase();
	} else {
		hex = hexToColor(hex);
		prefix = '0x';
	}

	if (hex.length > 4) {
		return hex;
	}

	let temp = hex.substr(1);
	hex = prefix;
	temp = /^([a-f0-9])([a-f0-9])([a-f0-9])$/i.exec(temp).slice(1);
	for (let i = 0; i < 3; i++) {
		hex += temp[i] + temp[i];
	}
	return hex;
}

function randColor() {
	return `#${`00000${((Math.random() * 0x1000000) << 0).toString(16)}`.substr(-6)}`;
}

export {
	calcLuminance,
	calcAccessibilityContrast,
	calcAccessibilityScore,
	isAccessible,
	isGray,
	isDark,
	isLight,
	calcColorDistance,
	calcNearestColor,
	calcBrightness,
	isValidColorString,
	colorToHexstr,
	colorToHex,
	colorToRGB,
	rgbToGrayscale,
	rgbToColor,
	rgbToHSL,
	colorToHSL,
	hexToARGB,
	hexToColor,
	hexToHexstr,
	hexToRGB,
	hexToRGBA,
	hexToString,
	hueToRGB,
	hslToRGB,
	hslToColor,
	rgbToHex,
	rgbToHexstr,
	rgbaToHexstr,
	rgba,
	hsvToRGB,
	rgbToHSV,
	hsvToColor,
	colorToHSV,
	rgbToCMYK,
	cmykToRGB,
	rgbToLCH,
	lchToRGB,
	formatColor,
	randColor
};
